package infovisproject;

import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PVector;
import processing.opengl.*;
import sun.tools.tree.Node;
import de.bezier.data.*;
import megamu.mesh.*;
import org.json.JSONObject;

public class InfovisProject extends PApplet {

	final boolean GET_JSON = false;
	int currentState;
	boolean todo;

	int cols[] = {
	  0, 47, 50, 33, 88, 207, 66, 130, 108, 116, 36, 246, 165, 199, 127, 210, 199, 240, 255, 200, 97, 228, 7, 98, 200, 70, 99, 87, 120, 103, 162, 163, 117, 237, 206, 130, 226, 42, 99, 214, 134, 68, 192, 220, 55, 171, 50, 41, 136, 173, 55, 102, 40, 69, 0, 60, 61, 12, 77, 207, 24, 95, 97, 89, 175, 165, 154, 255, 232, 124, 177, 159, 94, 99, 86, 75, 74, 66, 56, 49, 46, 89, 36, 54, 127, 187, 188, 166, 83, 66, 204, 162, 210, 242, 242, 97, 154, 179, 132, 66, 116, 166, 51, 76, 128, 36, 36, 89, 10, 12, 38, 33, 17, 64, 56, 22, 89, 98, 28, 238, 140, 35, 131, 165, 169, 242, 191, 48, 96, 216, 205, 79, 242, 106, 61, 52, 89, 125, 50, 68, 215, 48, 48, 48, 93, 73, 217, 138, 99, 129, 172, 240, 110, 207, 125, 91, 194, 138, 219, 181, 152, 90, 0, 22, 55, 26, 139, 199, 53, 1, 87, 91, 129, 217, 130, 2, 90, 174, 28, 204, 218, 55, 61, 236, 203, 58, 255, 95, 54, 31, 10, 38, 46, 16, 50, 61, 22, 61, 105, 171, 75, 150, 64, 89, 192, 211, 86, 235, 102, 83, 245, 96, 47, 255, 90, 10, 42, 66, 39, 148, 253, 152, 255, 185, 8, 254, 30, 24, 252, 131, 40, 253, 238, 177, 255, 90, 57, 249, 47, 198, 243, 5, 82
	};
	int col_party[];

	XlsReader reader, reader2;
	ArrayList<Node> nodes;
	ArrayList<Link>links;

	int nNode, nLink, nParty;

	float[][] points;
	Voronoi myVoronoi;
	Hull[] myHull;
	MPolygon[] myRegions;
	MPolygon[] myHullions;
	Delaunay[] myDelaunay;

	int font_size;
	PFont font;

	public void setup() {

	  size(1024, 768, OPENGL);

	  noStroke();
	  fill(0);
	  background( 255 );
	  //smooth();

	  font_size = 10;
	  println(PFont.list());
	  font = createFont("Nanum Gothic", font_size);
	  textFont(font, font_size);
	  textAlign(CENTER, CENTER);

	  reader = new XlsReader( this, "proc_node.xls" );
	  reader2 = new XlsReader( this, "proc_link.xls" );

	  nodes = new ArrayList<Node>();

	  reader.firstRow();

	  reader.nextRow();

	  int id = reader.getInt();
	  reader.nextCell();
	  reader.nextCell();
	  reader.nextCell();
	  reader.nextCell();
	  String name = reader.getString();
	  reader.nextCell();
	  float posX = reader.getFloat();
	  reader.nextCell();
	  float posY = reader.getFloat();
	  reader.nextCell();
	  String comm = reader.getString();
	  reader.nextCell();
	  int comm_id = reader.getInt();

	  Node n = new Node(id, name, comm, comm_id, posX, posY);
	  //  nodes.add(n);

	  while ( reader.hasMoreRows () ) {
	    reader.nextRow();

	    id = reader.getInt();
	    reader.nextCell();
	    reader.nextCell();
	    reader.nextCell();
	    reader.nextCell();
	    name = reader.getString();
	    reader.nextCell();
	    posX = reader.getFloat();
	    reader.nextCell();
	    posY = reader.getFloat();
	    reader.nextCell();
	    comm = reader.getString();
	    reader.nextCell();
	    comm_id = reader.getInt();

	    n = new Node(id, name, comm, comm_id, posX, posY);
	    nodes.add(n);
	  }

	  links = new ArrayList<Link>();

	  reader2.firstRow();

	  reader2.nextRow();

	  id = 1;
	  int nA = reader2.getInt();
	  reader2.nextCell();
	  int nB = reader2.getInt();
	  reader2.nextCell();
	  reader2.nextCell();
	  reader2.nextCell();
	  float weight = reader2.getFloat();
	  reader2.nextCell();
	  float nw = reader2.getFloat();

	  Link l = new Link(id, nA, nB, weight, nw);
	  links.add(l);

	  if (nA < nodes.size() && nB < nodes.size()) {
	    n = (Node)nodes.get(nA);
	    n.nLink++;
	    n = (Node)nodes.get(nB);
	    n.nLink++;
	  }
	  while ( reader2.hasMoreRows () ) {
	    reader2.nextRow();
	    id++;
	    nA = reader2.getInt();
	    reader2.nextCell();
	    nB = reader2.getInt();
	    reader2.nextCell();
	    reader2.nextCell();
	    reader2.nextCell();
	    weight = reader2.getFloat();
	    reader2.nextCell();
	    nw = reader2.getFloat();

	    l = new Link(id, nA, nB, weight, nw);
	    links.add(l);

	    if (nA < nodes.size() && nB < nodes.size()) {
	      n = (Node)nodes.get(nA);
	      n.nLink++;
	      n = (Node)nodes.get(nB);
	      n.nLink++;
	    }
	  }

	  nParty = 8;  
	  col_party = new int[nParty];
	  for (int i=0; i<col_party.length; i++) {
	    col_party[i] = ( (int)random(cols.length/3) ) * 3;
	  }

	  myHull = new Hull[nParty];
	  myHullions = new MPolygon[nParty];

	  myDelaunay = new Delaunay[nParty];

	  currentState = 0;
	  todo = true;
	  
	  if (GET_JSON) {
		  saveNode(nodes);
		  saveLink(links);
	  }
	}

	public void draw() {
	  background(255);
	  stroke(200, 50);

	  Node n;


	  for (int i=0; i<links.size(); i++) {
	    Link l = (Link)links.get(i);

	    if (l.nodeA < nodes.size() && l.nodeB < nodes.size()) {
	      strokeWeight(l.nw);

	      Node n1 = (Node)nodes.get(l.nodeA);
	      Node n2 = (Node)nodes.get(l.nodeB);

	      line(n1.pos.x, n1.pos.y, n2.pos.x, n2.pos.y);
	    }
	  }


	  float dis;
	  float tmp;
	  int highlight;

	  switch (currentState) {
	  case 0:
	    dis = Float.MAX_VALUE;
	    highlight = Integer.MIN_VALUE;

	    for (int i=0; i<nodes.size(); i++) {
	      n = (Node)nodes.get(i);
	      tmp = (n.pos.x-mouseX)*(n.pos.x-mouseX) + (n.pos.y-mouseY)*(n.pos.y-mouseY);
	      if (tmp < dis) {
	        dis = tmp;
	        highlight = i;
	      }
	    }

	    for (int i=0; i<nodes.size(); i++) {
	      if (i != highlight) {
	        n = (Node)nodes.get(i);
	        n.update();
	      }
	    }

	    n = (Node)nodes.get(highlight);
	    n.highlight();

	    break;

	  case 1:
	    if (todo) {
	      points = new float[nodes.size()][2];
	      for (int i=0; i<nodes.size(); i++) {
	        n = (Node)nodes.get(i);
	        points[i][0] = n.pos.x + random(0.1f);
	        points[i][1] = n.pos.y + random(0.1f);
	      }

	      myVoronoi = new Voronoi( points );
	      myRegions = myVoronoi.getRegions();

	      todo = false;
	    }

	    dis = Float.MAX_VALUE;
	    highlight = Integer.MIN_VALUE;

	    for (int i=0; i<myRegions.length; i++) {
	      n = (Node)nodes.get(i);
	      tmp = (n.pos.x-mouseX)*(n.pos.x-mouseX) + (n.pos.y-mouseY)*(n.pos.y-mouseY);
	      if (tmp < dis) {
	        dis = tmp;
	        highlight = i;
	      }
	    }

	    for (int i=0; i<myRegions.length; i++) {
	      n = (Node)nodes.get(i);

	      float[][] regionCoordinates = myRegions[i].getCoords();

	      if (i==highlight)
	        fill(cols[col_party[n.comm_id]], cols[col_party[n.comm_id]+1], cols[col_party[n.comm_id]+2], n.nLink*4);
	      else
	        fill(cols[col_party[n.comm_id]], cols[col_party[n.comm_id]+1], cols[col_party[n.comm_id]+2], n.nLink);

	      //myRegions[i].draw(this);
	      float[][] indice;
	      float[] pB = new float[2];
	      indice = myRegions[i].getCoords();

	      int i0, i1, i2;      

	      beginShape();
	      vertex( (indice[0][0]+indice[1][0])/2, (indice[0][1]+indice[1][1])/2 );
	      for (int _n=0; _n<indice.length; _n++) {
	        i0 = _n; 
	        i1 = _n+1; 
	        i2 = _n+2;
	        if (_n == indice.length-2) { 
	          i2 = 0;
	        } 
	        if (_n == indice.length-1) { 
	          i1 = 0; 
	          i2 = 1;
	        }

	        //        bezierVertex(indice[i1][0]/2+n.pos.x/2, indice[i1][1]/2+n.pos.y/2, indice[i1][0]/2+n.pos.x/2, indice[i1][1]/2+n.pos.y/2, 
	        bezierVertex(indice[i1][0], indice[i1][1], indice[i1][0], indice[i1][1], 
	        (indice[i1][0]+indice[i2][0])/2, (indice[i1][1]+indice[i2][1])/2 );
	      }
	      endShape();

	      points[i][0] = n.pos.x;
	      points[i][1] = n.pos.y;
	      fill(cols[col_party[n.comm_id]], cols[col_party[n.comm_id]+1], cols[col_party[n.comm_id]+2]);
	      ellipse(n.pos.x, n.pos.y, 5, 5);
	    }  

	    break;

	  case 2:
	    if (todo) {
	      ArrayList partedNodes;
	      float[] averageDistance;

	      for (int i=0; i<nParty; i++) {
	        partedNodes = getPartedNodes(i, 10);

	        for (int _i=0; _i<partedNodes.size(); _i++) {
	          n = (Node)partedNodes.get(_i);
	          points[_i][0] = n.pos.x;
	          points[_i][1] = n.pos.y;
	        }
	        myHull[i] = new Hull( points );
	        myHullions[i] = myHull[i].getRegion();

	        myDelaunay[i] = new Delaunay( points );
	      }
	      todo = false;
	    }

	    //    for (int i=0; i<nParty; i++) {
	    int i = 1;
	    {
	      fill(cols[col_party[i]], cols[col_party[i]+1], cols[col_party[i]+2], 100);

	      //myHullions[i].draw(this);

	      float[][] myEdges = myDelaunay[i].getEdges();

	      stroke(cols[col_party[i]], cols[col_party[i]+1], cols[col_party[i]+2], 100);
	      for (int _i=0; _i<myEdges.length; _i++) {
	        float startX = myEdges[_i][0];
	        float startY = myEdges[_i][1];
	        float endX = myEdges[_i][2];
	        float endY = myEdges[_i][3];
	        line( startX, startY, endX, endY );
	      }
	    }

	    break;
	  }
	}

	ArrayList<Node> getPartedNodes(int idx_party, float minNLinks) {
	  ArrayList<Node> returnArr = new ArrayList<Node>();

	  for (int i=0; i<nodes.size(); i++) {
	    Node n = (Node)nodes.get(i);
	    if (n.comm_id == idx_party && n.nLink > minNLinks) {
	      returnArr.add(n);
	    }
	  }

	  return returnArr;
	}

	public void keyPressed() {

	  currentState++;

	  if (currentState > 2)
	    currentState = 0;
	  else if (currentState < 0)
	    currentState = 2;

	  todo = true;
	}
	
	private void saveNode(ArrayList<Node> nodes) {
		try {
			FileWriter fw = new FileWriter("./data/nodes.json");
			JSONObject nodesJson = new JSONObject ();
			ArrayList<JSONObject> jsonArr = new ArrayList<JSONObject>();
			for (Node n : nodes) {
				jsonArr.add(n.toJson());
			}
			nodesJson.put("nodes", jsonArr);
			nodesJson.write(fw);
			fw.flush();
			fw.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	private void saveLink(ArrayList<Link> links) {
		try {
			FileWriter fw = new FileWriter("./data/links.json");
			JSONObject nodesJson = new JSONObject ();
			ArrayList<JSONObject> jsonArr = new ArrayList<JSONObject>();
			for (Link l : links) {
				jsonArr.add(l.toJson());
			}
			nodesJson.put("links", jsonArr);
			nodesJson.write(fw);
			fw.flush();
			fw.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	

	class Node {
	  PVector pos;
	  int id, comm_id;
	  String name;
	  ArrayList clusters;

	  int nLink;
	  float radius;

	  Node(int _id, String _name, String _comm, int _comm_id, float _posX, float _posY) {
	    id = _id;
	    name = _name;
	    comm_id = _comm_id;
	    pos = new PVector(map(_posX, 0, 10000, 20, width-20), map(_posY, 0, 10000, 20, height-20));

	    nLink = 0;
	  }

	  void update() {
	    radius = nLink * 1;

	    if (nLink > 30) {
	      fill(cols[col_party[comm_id]], cols[col_party[comm_id]+1], cols[col_party[comm_id]+2], 100);
	      ellipse(pos.x, pos.y, radius, radius);
	    }
	    fill(0);
	    text(name, pos.x, pos.y);
	  }

	  void highlight() {
	    radius = nLink;

	    fill(cols[col_party[comm_id]], cols[col_party[comm_id]+1], cols[col_party[comm_id]+2], 100);
	    ellipse(pos.x, pos.y, radius, radius);
	    strokeWeight(4);
	    stroke(cols[col_party[comm_id]], cols[col_party[comm_id]+1], cols[col_party[comm_id]+2]);
	    noFill();
	    ellipse(pos.x, pos.y, radius, radius);

	    fill(0);
	    text(name, pos.x, pos.y);
	  }
	  
	  public JSONObject toJson() {
			
			HashMap map = new HashMap();
			map.put("id", id);
			map.put("comm_id", comm_id);
			float[] posArr = {pos.x, pos.y};
			map.put("pos", posArr);
			map.put("name", name);
			//map.put("community", community);
			map.put("nLink", nLink);
			
			JSONObject nodeJson = new JSONObject (map);
			return nodeJson;
			
		}
	}

	class Link {
	  int id;
	  int nodeA, nodeB;
	  float weight, nw;

	  Link(int _id, int nA, int nB, float _weight, float _nw) {
	    id = _id;
	    nodeA = nA;
	    nodeB = nB;
	    weight = _weight;
	    nw = _nw;
	  }
	  
	  public JSONObject toJson() {
		  HashMap map = new HashMap();
		  map.put("id", id);
		  map.put("nodeA", nodeA);
		  map.put("nodeB", nodeB);
		  map.put("weight", weight);
		  map.put("nw", nw);
		  
		  JSONObject nodeJson = new JSONObject (map);
		  return nodeJson;
	  }
	}

}
